package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotations.*;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class MyDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    //缓存扫描到的类的全限定类名
    private List<String> classNames = new ArrayList<>();

    //ioc容器
    private Map<String, Object> ioc = new HashMap<>();

    //url和method之间的映射关系
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //根据url获取对应的handler
        String url = req.getRequestURI();
        Handler handler =  getHandler(url);
        if(handler == null){
            resp.getWriter().print("404 not found");
            return ;
        }
        //封装参数
        //获取参数请求类型数组
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        //创建请求参数值数组 长度为parameterTypes长度
        Object[] objects = new Object[parameterTypes.length];

        Map<String, Integer> initParameterMap = handler.getParameterMap();
        //向参数数组中放入值，并按照参数顺序放入
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] strings = parameterMap.get(key);
            String value = StringUtils.join(strings, ",");
            if(!initParameterMap.containsKey(key)){continue;}
            //如果找到此参数 获取该参数的索引位置
            Integer index = initParameterMap.get(key);
            objects[index] = value;
            //权限校验,判断当前请求的用户名是否有访问当前handler的权限
            if(!handler.getUserNames().contains(value)){
                resp.setHeader("content-type", "text/html;charset=utf-8");
                resp.getWriter().write("当前用户无权限访问.");
                return;
            }
        }

        //封装request 与response参数
        Integer responseIndex = initParameterMap.get(HttpServletResponse.class.getSimpleName());
        Integer requestIndex = initParameterMap.get(HttpServletRequest.class.getSimpleName());


        objects[requestIndex] = req;
        //创建参数数组 长度为parameterMap大小
        objects[responseIndex] = resp;
        //利用反射调用被执行行的方法
        try {
            handler.getMethod().invoke(handler.getController(),objects);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化springMvc容器
        System.out.println("springMvc容器初始化.......");
        //1.加载配置文件
        //获取配置信息
        String myContextConfigLocation = config.getInitParameter("myContextConfigLocation");
        loadConfig(myContextConfigLocation);

        //2.扫描包及注解
        doScanPage(properties.getProperty("scanPage"));

        //3.初始化bean对象（实现IOC容器，基于注解方式）
        doInstance();

        //4.实现依赖注入
        doAutoWired();

        //5.构造一个handlerMapping处理器映射器 将配置好的url与method建立映射关系
        initHanderMapping();

        //6.等待请求进入处理请求
        super.init(config);
    }

    /**
     * 实现url与handler的映射
     */
    private void initHanderMapping() {
        //如果容器为为空 直接return 返回
        if (ioc.isEmpty()) return;

        //不是空 遍历Ioc容器 实现url与handler的映射
        Set<Map.Entry<String, Object>> entries = ioc.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String beanName = entry.getKey();
            Object object = entry.getValue();
            Class<?> aClass = object.getClass();
            //如果不是controller则无需进行映射
            if (!aClass.isAnnotationPresent(MyController.class)) continue;
            ;

            //如果是:1.判断controller上是否被@MyRequestMapping修饰 2.逐一判断类中的方法是否被@MyRequestMapping修饰
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping myRequestMapping = aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = myRequestMapping.value(); // 例如：@MyRequestmapping（“/demo”）value为 /demo
            }
            //有权限访问的用户名集合
            Set<String> controllerUserNames = new HashSet<>();
            //如果类被@MySecurity注解修饰
            if(aClass.isAnnotationPresent(MySecurity.class)){
                //获取有权限访问的用户
                String[] value = aClass.getAnnotation(MySecurity.class).value();
                controllerUserNames.addAll(Arrays.stream(value).collect(Collectors.toSet()));
            }

            //逐一判断类中的方法是否被@MyRequestMapping修饰
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                //判断是否被@MyRequestMapping修饰
                if (!method.isAnnotationPresent(MyRequestMapping.class)) continue;

                //被修饰
                MyRequestMapping MyRequestMapping = method.getAnnotation(MyRequestMapping.class);
                String methodUrl = MyRequestMapping.value();
                String completeUrl = baseUrl + methodUrl;

                //如果方法上被@MySecurity注解修饰
                Set<String> methodUsernames = new HashSet<>();
                methodUsernames.addAll(controllerUserNames);
                if(method.isAnnotationPresent(MySecurity.class)){
                    //获取有权限访问的用户 并加到useNames集合中
                    String[] value = method.getAnnotation(MySecurity.class).value();
                    methodUsernames.addAll(Arrays.stream(value).collect(Collectors.toSet()));
                }

                Handler handler = new Handler(object, method, Pattern.compile(completeUrl),methodUsernames);

                //封装参数map  key为参数名称 value为数组中索引  eg:   String query(HttpServletRequest request, HttpServletResponse response, String name)
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameterType = parameters[i];
                    //如果参数是request或response对象 那么参数名称为参数类型 即key为参数类型
                    if (parameterType.getType() == HttpServletRequest.class || parameterType.getType() == HttpServletResponse.class) {
                        handler.getParameterMap().put(parameterType.getType().getSimpleName(),i);
                    }
                    //如果不是，key为参数名称
                    handler.getParameterMap().put(parameterType.getName(),i);
                }

                //存储url和method之间的映射关系
                handlerMapping.add(handler);
            }

        }


    }

    /**
     * 实现依赖注入
     */
    private void doAutoWired() {
        if (ioc.isEmpty()) return;
        Set<Map.Entry<String, Object>> entries = ioc.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            //beanName
            String name = entry.getKey();
            //实例对象
            Object object = entry.getValue();
            //获取对象中的字段信息
            Field[] declaredFields = object.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) { //  @LagouAutowired  private IDemoService demoService;
                //判断字段是否被@LagouAutowired 修饰
                if (!declaredField.isAnnotationPresent(MyAutoWired.class)) continue;

                // 有该注解
                MyAutoWired myAutoWired = declaredField.getAnnotation(MyAutoWired.class);
                String beanName = myAutoWired.value();
                if ("".equals(beanName)) {
                    //没有配置具体的bean Id,以当前字段的类型（接口注入）   IDemoService
                    beanName = declaredField.getType().getSimpleName();
                }
                //开启赋值
                declaredField.setAccessible(true);

                try {
                    declaredField.set(object, ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /**
     * 将扫描到的类实例化
     */
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                //区分 controller service
                if (aClass.isAnnotationPresent(MyController.class)) {//controller
                    //这里controller不考虑注解是否存在指定 将类名首字母变小写
                    String simpleName = aClass.getSimpleName();
                    String lowerFirstName = toLowerFirst(simpleName);
                    //实例化对象
                    Object o = aClass.newInstance();
                    //存储到容器中
                    ioc.put(lowerFirstName, o);
                } else if (aClass.isAnnotationPresent(MyService.class)) {//service
                    //Service要获取是否存在指定值 若存在以指定值为key，反之以类名首字母小写为key
                    MyService myService = aClass.getDeclaredAnnotation(MyService.class);
                    String beanName = myService.value();
                    if (!"".equals(beanName.trim())) {//以beanName为key
                        ioc.put(beanName, aClass.newInstance());
                    } else {//以类名首字母小写为key
                        ioc.put(toLowerFirst(aClass.getSimpleName()), aClass.newInstance());
                    }


                    //service层往往是有接口的，面向接口开发，此时以接口名为id,存入ioc容器中，便于后期接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        // 以接口的全限定类名作为id放入ioc容器
                        String simpleName = anInterface.getSimpleName();
                        ioc.put(simpleName, aClass.newInstance());
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将首字母变小写
     */
    private String toLowerFirst(String name) {
        char[] chars = name.toCharArray();
        if ('A' < chars[0] && chars[0] < 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);

    }

    /**
     * 将所有类的全限定名并存储到内存中
     * 思路：读取磁盘文件的方式扫描类 例如：com.lagou.demo 相当于 com/lagou/demo
     */
    private void doScanPage(String scanPage) {
        //获取项目根目录  getResource为空就相当于获取项目根目录
        String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //将 . 换为 / 获取扫描包的目录
        String s = rootPath + scanPage.replace(".", "/");
        File pack = new File(s);
        //获取所有子package
        File[] files = pack.listFiles();
        for (File file : files) {
            //判断是否是子package
            if (file.isDirectory()) {//是
                //递归查询
                doScanPage(scanPage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {//是java类
                String className = scanPage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private void loadConfig(String myContextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(myContextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //根据url获取对应的handler
    private Handler getHandler(String url){
        if(handlerMapping.isEmpty()){return null;}
        for(Handler handler : handlerMapping){
            Matcher matcher = handler.getPattern().matcher(url);
            if(matcher.matches()){
                return handler;
            }
        }
        return null;
    }
}
